from engine.application import Application
from engine.service import (
    ServiceManager, GameObjectService, 
    PhysicsService, GraphicsService,
    InputService, ResourceService,
    AbstractService
)
from engine.object import (
    GameObject, GraphicalObject,
    PhysicalObject, CombinedObject
)
import pyglet
import pymunk
from pymunk import Vec2d
import random
import math


"""def centrify(path):
    image = pyglet.image.load(path)
    image.anchor_x = image.width / 2
    image.anchor_y = image.height / 2
    return image"""

class SpaceShip(CombinedObject):
    image_path = "spaceship.png"
    points = [(32, 0),
              (-32, 32),
              (-32, -32)]
    mass = 1
    maximum_speed = 350
    group = 1
    
    def __init__(self, *args, **kwargs):
        """
        Create a new spaceship object. 
        """
        super(SpaceShip, self).__init__(*args, **kwargs)
        
        self.is_turning_left = False
        self.is_turning_right = False
        self.is_accellerating = False
        self.is_shooting = False
        self.is_special = False
        
        self.next_shot = 0.
        self.next_special = 0.
        
        self.accelleration = 30
        self.turning_speed = 4
        self.body.angular_velocity = 0
    
    def update(self, dt):
        if self.is_turning_left and not self.is_turning_right:
            self.body.angular_velocity = self.turning_speed
        elif self.is_turning_right and not self.is_turning_left:
            self.body.angular_velocity = -self.turning_speed
        
        if self.is_accellerating:
            self.body.apply_force(self.body.rotation_vector * 
                                  self.accelleration)
        else:
            self.body.reset_forces()
        
        if self.next_shot > 0.:
            self.next_shot -= dt
        if self.next_special > 0.:
            self.next_special -= dt
        
        if self.is_shooting and self.next_shot <= 0.:
            # reset time to next shot
            self.next_shot = 0.5
            
            # spawn a new Shot object
            position = self.body.position + self.body.rotation_vector * 40
            velocity = self.body.rotation_vector * 300 + self.body.velocity
            self.object_service.add_object(Shot(position = position,
                                                velocity = velocity,
                                                angle = self.body.angle))
        
        if self.is_special and self.next_special <= 0.:
            start = self.body.position
            end = self.body.position + self.body.rotation_vector * 10000
            
            physicsservice = ServiceManager.instance[PhysicsService]
            info = physicsservice.segment_query(start, end, True, group = 1)
            
            if info is not None:
                self.next_special = 0.5
                position = self.body.position + self.body.rotation_vector * 40
                velocity = self.body.rotation_vector * 100 + self.body.velocity
                missile = Missile(target = info.shape.body.object,
                                  position = position,
                                  velocity = velocity,
                                  angle = self.body.angle)
                self.object_service.add_object(missile)
            
        
    def turn_left(self, value = True):
        self.is_turning_left = value
        if not value and not self.is_turning_right:
            self.body.angular_velocity = 0

    def turn_right(self, value = True):
        self.is_turning_right = value
        if not value and not self.is_turning_left:
            self.body.angular_velocity = 0
        
    def on_collision(self, other, arbiter):
        if isinstance(other, Asteroid):
            #spawn an explosion
            self.object_service.add_object(Explosion(position = arbiter.contacts[0].position))
            self.object_service.add_object(Explosion(position = self.body.position,
                                                     scale = 3))                                      
            self.object_service.remove_object(self)
            return True
        else:
            return False  
            
class Asteroid(CombinedObject):
    image_path = "asteroid.png"
    radius = 32
    mass = 1
    
    def on_collision(self, other, arbiter):
        if isinstance(other, Shot) or isinstance(other, SpaceShip):
            self.object_service.remove_object(self)
            
            if self.scale > 0.75:
                direction = other.body.velocity
                direction.rotate(math.pi/2)
                
                velocity = direction.normalized() * 300 + self.body.velocity
                position = self.body.position
                self.object_service.add_object(Asteroid(position = position,
                                                        velocity = velocity,
                                                        scale = self.scale/2))
                self.object_service.add_object(Asteroid(position = position,
                                                        velocity = -velocity,
                                                        scale = self.scale/2))
        # return true anyways
        return True
            
class Shot(CombinedObject):
    image_path = "shot.png"
    points = [(8, 0),
              (-8, 4),
              (-8, -4)]
    mass = 1
    maximum_speed = SpaceShip.maximum_speed * 2
    
    def __init__(self, *args, **kwargs):
        super(Shot, self).__init__(*args, **kwargs)
        self.lifetime = 2.
    
    def update(self, dt):
        self.lifetime -= dt
        if self.lifetime <= 0.:
            self.object_service.remove_object(self)
        
    def on_collision(self, other, arbiter):
        if isinstance(other, Asteroid):
            #spawn an explosion
            explosion = Explosion(position = arbiter.contacts[0].position)
            self.object_service.add_object(explosion)
            self.object_service.remove_object(self)
            return True
        else:
            return False

class Missile(Shot):
    image_path = "missile.png"
    maximum_speed = SpaceShip.maximum_speed

    def __init__(self, target, *args, **kwargs):
        super(Missile, self).__init__(*args, **kwargs)
        self.target = target
        self.lifetime = 10.
    
    def update(self, dt):
        #TODO: FIXME
        Shot.update(self, dt)
        
        mis_pos = self.body.position
        tar_pos = self.target.body.position
        tar_vel = self.target.body.velocity
        
        desired_vector = (tar_pos - mis_pos + tar_vel) / self.maximum_speed
        missile_dir = self.body.rotation_vector
        
        angle_diff = desired_vector.get_angle_between(missile_dir)
        self.body.angular_velocity = -angle_diff
        self.body.apply_force(missile_dir * 20)
    
    
class Explosion(GraphicalObject):
    animation_tiling = (4, 4)
    animation_duration = 0.5
    
    def __init__(self, *args, **kwargs):
        if 'animation_path' not in kwargs:
            kwargs['animation_path'] = "explosion%d.png" % random.randint(0, 6)
        super(Explosion, self).__init__(*args, **kwargs)
    
    def on_animation_end(self):
        self.object_service.remove_object(self)

        
class YaaGameService(AbstractService):
    
    def __init__(self):
        self.is_ship_dead = True
        self.ship_respawn_timer = 0.
        
    def on_object_added(self, obj):
        if isinstance(obj, SpaceShip):
            self.is_ship_dead = False
            
            # set up SpaceShip input event handlers
            self.mgr[InputService].register_input_handler(pyglet.window.key.A, obj, 'turn_left')
            self.mgr[InputService].register_input_handler(pyglet.window.key.D, obj, 'turn_right')
            self.mgr[InputService].register_input_handler(pyglet.window.key.W, obj, 'is_accellerating')
            self.mgr[InputService].register_input_handler(pyglet.window.key.SPACE, obj, 'is_shooting')
            self.mgr[InputService].register_input_handler(pyglet.window.key.LCTRL, obj, 'is_special')
    
    def on_object_removed(self, obj):
        if isinstance(obj, SpaceShip):
            self.is_ship_dead = True

    def on_tick(self, dt):
        if self.is_ship_dead:
            self.ship_respawn_timer -= dt
            if self.ship_respawn_timer <= 0.:
                ship = SpaceShip(position = (450, 450))
                self.mgr[GameObjectService].add_object(ship)
                self.ship_respawn_timer = 2.
    
class YaaGame(Application):
    window_size = 900, 900
    
    def setup(self):
        # set up game services
        mgr = self.mgr
        bounds = (-10, -10, self.window_size[0] + 10, self.window_size[1] + 10)
        mgr += PhysicsService(bounds = bounds)
        mgr += GraphicsService()
        mgr += GameObjectService()
        mgr += InputService(window = self.window)
        mgr += ResourceService()
        mgr += YaaGameService()

        # setup resource locations
        mgr[ResourceService].add_resource_location("graphics", "sounds")

        mgr[InputService].register_input_handler(pyglet.window.key.R, self.mgr[PhysicsService], 
                                                 'switch_debug_draw')

        # create a SpaceShip object and add it to the object manager
        #ship = SpaceShip(position = (self.window_size[0]/2, self.window_size[1]/2))
        #mgr[GameObjectService].add_object(ship)

        # create some Asteroids and add them to the object manager
        for i in range(5):
            position = random.random() * self.window_size[0], random.random() * self.window_size[1]
            velocity = (random.random() - 0.5) * 100, (random.random() - 0.5) * 100
            scale = random.random() + 0.5
            asteroid = Asteroid(position = position, velocity = velocity, scale = scale)
            
            self.mgr[GameObjectService].add_object(asteroid)

if __name__ == "__main__":
    app = YaaGame()
    app.run()